package master.ghrm.domain;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapKey;
import javax.persistence.OneToMany;
import javax.persistence.Version;

import org.apache.commons.collections.CollectionUtils;
import org.hibernate.annotations.Type;
import org.joda.time.DateTime;

@Entity
public class Assessment extends AbstractBaseEntity {

	private static final long serialVersionUID = 1L;

	public static final int LENGTH_CONCLUSION = 10000;

	@Version
	private Long version;

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "assessment_id")
	private Integer assessmentId;

	@ManyToOne(fetch = FetchType.EAGER)
	@JoinColumn(name = "evaluator_id", nullable = false)
	private Evaluator evaluator;

	@ManyToOne(fetch = FetchType.EAGER)
	@JoinColumn(name = "evaluation_id", nullable = false)
	private Evaluation evaluation;

	@Column(name = "eval_time")
	@Type(type = "org.joda.time.contrib.hibernate.PersistentDateTime")
	private DateTime evalTime;

	@Column(name = "published")
	private Boolean published = false;

	@Column(name = "task_id")
	private Long taskId;

	@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "assessment")
	private List<Answer> answers = new ArrayList<Answer>(0);

	@OneToMany(mappedBy = "id.assessment")
	@MapKey(name = "id.category")
	private Map<EvalCategory, CategoryElaboration> elaborations;

	@Column(length = LENGTH_CONCLUSION)
	private String conclusion;

	@Column(name = "is_interview", nullable = false)
	private Boolean isInterview = false;

	/**
	 * Default constructor for persistence mechanisms.
	 */
	public Assessment() {
	}

	/**
	 * Full constructor.
	 * 
	 * @param anEvaluator person doing this assessment.
	 * @param anEvaluation evaluation this assessment belongs to.
	 */
	public Assessment(Evaluator anEvaluator, Evaluation anEvaluation) {
		this.evaluator = anEvaluator;
		this.evaluation = anEvaluation;
	}

	/**
	 * @return assessment ID.
	 */
	public Integer getAssessmentId() {
		return this.assessmentId;
	}

	/**
	 * @param anAssessmentId assessment ID to set.
	 */
	public void setAssessmentId(Integer anAssessmentId) {
		this.assessmentId = anAssessmentId;
	}

	/**
	 * Optimistic locking version property.
	 * 
	 * @return version.
	 */
	Long getVersion() {
		return this.version;
	}

	/**
	 * @param aVersion version of the entity.
	 */
	void setVersion(Long aVersion) {
		this.version = aVersion;
	}

	/**
	 * @return evaluator.
	 */
	public Evaluator getEvaluator() {
		return this.evaluator;
	}

	/**
	 * @param anEvaluator evaluator to set.
	 */
	public void setEvaluator(Evaluator anEvaluator) {
		this.evaluator = anEvaluator;
	}

	/**
	 * @return evaluation of this assessment.
	 */
	public Evaluation getEvaluation() {
		return this.evaluation;
	}

	/**
	 * @param anEvaluation evaluation to set.
	 */
	public void setEvaluation(Evaluation anEvaluation) {
		this.evaluation = anEvaluation;
	}

	/**
	 * @return evaluation time.
	 */
	public DateTime getEvalTime() {
		return this.evalTime;
	}

	/**
	 * @param anEvalTime evaluation time.
	 */
	public void setEvalTime(DateTime anEvalTime) {
		this.evalTime = anEvalTime;
	}

	/**
	 * @return if this assessment has been published.
	 */
	public Boolean getPublished() {
		return this.published;
	}

	/**
	 * @param isPublished is this assessment has been published.
	 */
	public void setPublished(Boolean isPublished) {
		this.published = isPublished;
	}

	/**
	 * @return list of answers for this assessment.
	 */
	public List<Answer> getAnswers() {
		return this.answers;
	}

	/**
	 * @param answerList list of answers to set.
	 */
	public void setAnswers(List<Answer> answerList) {
		this.answers = answerList;
	}

	/**
	 * @param answer answer to add to the list.
	 */
	public void addAnswer(Answer answer) {
		this.answers.add(answer);
	}

	/**
	 * @return the elaborations
	 */
	public Map<EvalCategory, CategoryElaboration> getElaborations() {
		return elaborations;
	}

	/**
	 * @param elaborationList the elaborations to set
	 */
	public void setElaborations(Map<EvalCategory, CategoryElaboration> elaborationList) {
		this.elaborations = elaborationList;
	}

	/**
	 * @return evaluation conclusion.
	 */
	public String getConclusion() {
		return conclusion;
	}

	/**
	 * @param aConclusion evaluation conclusion.
	 */
	public void setConclusion(String aConclusion) {
		this.conclusion = aConclusion;
	}

	public Map<EvalCategory, List<Answer>> getAnswersByCategories() {
		Map<EvalCategory, List<Answer>> answersMap = new HashMap<EvalCategory, List<Answer>>();
		if (answers != null) {
			for (Answer answer : answers) {
				EvalCategory evalCategory = answer.getQuestion().getEvalCategory();
				List<Answer> categoryAnswers = answersMap.get(evalCategory);
				if (categoryAnswers == null) {
					categoryAnswers = new ArrayList<Answer>();
					answersMap.put(evalCategory, categoryAnswers);
				}
				categoryAnswers.add(answer);
			}
		}
		return answersMap;
	}

	/**
	 * Initialize the list of answers, after the properties have been set.
	 */
	public boolean initAnswers() {
		final List<EvalCategory> categories = evaluation.getQuestioneer().getEvalCategories();

		final List<Question> questions = new ArrayList<Question>();
		for (final EvalCategory evalCategory : categories) {
			questions.addAll(evalCategory.getQuestions());
		}

		boolean hasAnswers = hasAnswers();
		if (CollectionUtils.isNotEmpty(answers)) {
			for (final Answer answer : answers) {
				if (questions.contains(answer.getQuestion())) {
					questions.remove(answer.getQuestion());
				}
			}
		}

		for (final Question question : questions) {
			switch (question.getQuestionType().getQuestionTypeId()) {
			case 1:
				addAnswer(new ChoiceQuestionAnswer((ChoiceQuestion) question, this));
				break;
			case 2:
				addAnswer(new ChoiceQuestionAnswer((ChoiceQuestion) question, this));
				break;
			case 3:
				addAnswer(new TextualQuestionAnswer((TextualQuestion) question, this));
				break;
			case 4:
				addAnswer(new TextualQuestionAnswer((TextualQuestion) question, this));
				break;
			case 5:
				addAnswer(new GradableQuestionAnswer((GradableQuestion) question, this));
				break;
			default:
				throw new IllegalStateException("Unknown question type: " + question);
			}
		}

		return hasAnswers;
	}

	private boolean hasAnswers() {
		if (CollectionUtils.isEmpty(answers)) {
			return true;
		}

		boolean hasAnswers = false;
		for (final Answer answer : answers) {
			if (answer instanceof ChoiceQuestionAnswer) {
				ChoiceQuestionAnswer choiceQuestionAnswer = (ChoiceQuestionAnswer) answer;
				hasAnswers = CollectionUtils.isNotEmpty(choiceQuestionAnswer.getQuestionAnswers());
			}
		}

		return hasAnswers;
	}

	/**
	 * @return the task
	 */
	public Long getTaskId() {
		return this.taskId;
	}

	/**
	 * @param aTaskId the task to set
	 */
	public void setTaskId(Long aTaskId) {
		this.taskId = aTaskId;
	}

	/**
	 * @return the isInterview
	 */
	public Boolean getIsInterview() {
		return isInterview == null ? false : isInterview;
	}

	/**
	 * @param isInterviewValue the isInterview to set
	 */
	public void setIsInterview(Boolean isInterviewValue) {
		if (isInterviewValue == null) {
			throw new IllegalArgumentException("Is interview cannot be null.");
		}
		this.isInterview = isInterviewValue;
	}

	/**
	 * @see com.levi9.hrm.model.base.AbstractBaseEntity#getExcludedAttributes()
	 */
	@Override
	protected String[] getExcludedAttributes() {
		return new String[] { "answers" };
	}

}
